﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using GameObjects;
using Network;
using Virvar.Net;

namespace GameServer
{
    class Communicator
    {
        public int ServerConnectionPort = 7776;
        public int ServerPort = 7777;
        private TimeSpan connectionTimeout = new TimeSpan(0, 0, 5);

        private Dictionary<int, NetPlayer> _players = new Dictionary<int, NetPlayer>(); // для исходящих сообщений
        private Dictionary<int, NetPlayer> _newPlayers = new Dictionary<int, NetPlayer>(); // буфер для подключений игроков
        private IObjMessager _inputMessager; // для входящих сообщений
        private IObjMessager _connectionsMessager; // для входящих сообщений
        private int updateRate = 100; // период между обновлениями
        private bool shouldContinue = true;
        Stopwatch stopWatch = new Stopwatch();
        Timer sendTimer; // отправляет сообщения по таймеру
        Timer checkDisconnectionsTimer; // проверяет соединения по таймеру

        public Communicator()
        {
            _inputMessager = new ProtoMessager<ClientPacket>(new UdpMessager(ServerPort));
            _connectionsMessager = new ProtoMessager<ConnectionMessage>(new UdpMessager(ServerConnectionPort));

            // начинаем принимать соединения
            new Thread(new ThreadStart(StartReceivingConnnections)).Start();
            // начинаем принимать сообщения
            new Thread(new ThreadStart(StartReceiving)).Start();
            // начинаем отправлять сообщения
            sendTimer = new Timer(new TimerCallback(Send), null, 0, updateRate);
            // проверяем отключения игроков
            checkDisconnectionsTimer = new Timer(
                new TimerCallback(CheckDisconnections), null, connectionTimeout, connectionTimeout);
        }

        void StartReceivingConnnections()
        {
            // запускаем поток получения сообщений от клиентов
            while (shouldContinue)
            {
                IPEndPoint remoteEP = null;
                int packetLength;
                ConnectionMessage msg = (ConnectionMessage)_connectionsMessager.Receive(ref remoteEP, out packetLength);
                // новое подключение
                NetPlayer player = AddPlayer(remoteEP, msg);
                Console.WriteLine("Player {0} trying to connect.", player.PlayerId);
                Trace.WriteLine(string.Format("Player {0} trying to connect.", player.PlayerId));
                new Thread(new ParameterizedThreadStart(SendFirstPacket)).Start(player);
            }
        }

        void SendFirstPacket(object netPlayer)
        {
            NetPlayer player = (NetPlayer)netPlayer;
            ServerConnectionPacket outMsg = new ServerConnectionPacket();
            outMsg.PlayerId = player.PlayerId;
            outMsg.Speed = player.Character.Speed;
            outMsg.Position = player.Character.Position;
            for (int i = 0; _newPlayers.ContainsKey(player.PlayerId) && i < 10; i++)
            {
                player.Messager.Send<ServerConnectionPacket>(outMsg);
                Thread.Sleep(1000);
            }
            _newPlayers.Remove(player.PlayerId);
            if (_players.ContainsKey(player.PlayerId))
            {
                Console.WriteLine("Player {0} connected.", player.PlayerId);
                Trace.WriteLine(string.Format("Player {0} connected.", player.PlayerId));
            }
            else
            {
                Console.WriteLine("Player {0} failed to connect.", player.PlayerId);
                Trace.WriteLine(string.Format("Player {0} failed to connect.", player.PlayerId));
            }
        }

        void StartReceiving()
        {
            // запускаем поток получения сообщений от клиентов
            stopWatch.Start();
            while (shouldContinue)
            {
                Receive();
            }
        }

        // удаляем неактивных игроков
        void CheckDisconnections(object state)
        {
            HashSet<int> toRemove = new HashSet<int>();
            TimeSpan lastAdmission = stopWatch.Elapsed - connectionTimeout;
            foreach (var player in _players.Values)
            {
                if (player.LastUpdate < lastAdmission)
                    toRemove.Add(player.PlayerId);
            }
            foreach (var id in toRemove)
            {
                _players.Remove(id);
                Console.WriteLine("Player {0} disconnected.", id);
                Trace.WriteLine(string.Format("Player {0} disconnected.", id));
            }
        }

        void Stop()
        {
            shouldContinue = false;
            _inputMessager.Close();
            _players.Values.ToList().ForEach(p => p.Messager.Close());
        }

        // добавляем нового игрока в игру
        NetPlayer AddPlayer(IPEndPoint remoteEP, ConnectionMessage msg)
        {
            Character character = GameLogic.Instance.CreateCharacter(true);
            IMessager messager = new UdpMessager(remoteEP.Address.ToString(), msg.ClientPort);
            IObjMessager objMessager = new ProtoMessager<ServerPacket>(messager);
            NetPlayer player = new NetPlayer(objMessager, character, stopWatch.Elapsed);

            _newPlayers.Add(player.PlayerId, player);
            return player;
        }

        // отправка данных
        void Send(object state)
        {
            //foreach (var player in _players.Values)
            //{
            //    ServerPacket outMsg = new ServerPacket();
            //    outMsg.Sequence = player.MessageId++;
            //    outMsg.Ack = player.Ack;
            //    outMsg.PlayerId = player.PlayerId;
            //    outMsg.Position = player.Character.Position;
            //    msgs.Add(outMsg);
            //}
            ServerPacket outMsg = new ServerPacket(GameLogic.Instance.players.Count);
            foreach (var player in GameLogic.Instance.players)
            {
                PlayerInfo playerInfo = new PlayerInfo();
                playerInfo.PlayerId = (sbyte)player.Id;
                playerInfo.Position = player.Position;
                outMsg.PlayersStates.Add(playerInfo);
            }
            foreach (var player in _players.Values)
            {
                outMsg.Sequence = player.MessageId++;
                outMsg.Ack = player.Ack;
                player.Messager.Send(outMsg);
            }
        }

        // получение данных
        void Receive()
        {
            IPEndPoint remoteEP = null;
            int packetLength;
            ClientPacket msg = (ClientPacket)_inputMessager.Receive(ref remoteEP, out packetLength);
            if (_players.ContainsKey(msg.PlayerId))
            {
                NetPlayer player = _players[msg.PlayerId];
                if (msg.Sequence > player.Ack)
                {
                    TimeSpan elapsedTime = stopWatch.Elapsed - player.LastUpdate;
                    player.LastUpdate = stopWatch.Elapsed;
                    player.Ack = msg.Sequence;
                    player.Character.Position += msg.Direction * (float)(player.Character.Speed * elapsedTime.TotalSeconds);
                }
            }
            else if (_newPlayers.ContainsKey(msg.PlayerId)) // убеждаемся, что первый пакет дошёл до клиента
            {
                _players.Add(msg.PlayerId, _newPlayers[msg.PlayerId]);
                _newPlayers.Remove(msg.PlayerId);
            }
        }

        ~Communicator()
        {
            if (stopWatch != null)
                stopWatch.Stop();
            Stop();
        }
    }
}
